/*
 * File     : StructureFieldOrParameter.java
 * All Rights Reserved.
 */
package com.googlecode.dni.internal;

import static com.googlecode.dni.internal.DniTypes.POINTER_TYPE;
import static com.googlecode.dni.internal.DniTypes.STRUCTURE_HELPER_TYPE;
import static org.objectweb.asm.Opcodes.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.logging.Level;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import com.googlecode.dni.type.CustomNativeObject;
import com.googlecode.dni.type.ObjectReference;
import com.googlecode.dni.type.Pointer;

/**
 * Base interface for structure fields and parameters.
 *
 * @author Matthew Wilson
 */
public interface StructureFieldOrParameter
{

    /**
     * @return the Java type of the structure field or parameter
     */
    Class< ? > getJavaType();

    /**
     * Provides some helper functions.
     *
     * @author Matthew Wilson
     */
    public final class Helper
    {
        private Helper()
        {
            throw new UnsupportedOperationException();
        }

        /**
         * Finds a field name that can be shared between two instances of the
         * same structure field or parameter on the same type.
         *
         * @param <T>
         *            the structure field or parameter base type
         * @param <U>
         *            the specific type
         * @param createdFields
         *            the map of created fields; will be updated
         * @param owner
         *            the owning field or parameter
         * @param baseFieldName
         *            the base field name
         * @param fieldNameRef
         *            a reference that will be updated with the field name
         *
         * @return <code>true</code> iff this is the owner;
         *         <code>false</code> iff shared
         */
        public static < T extends StructureFieldOrParameter, U extends T >
        boolean findShareableField( final Map< String, T > createdFields,
                                    final U owner,
                                    final String baseFieldName,
                                    final ObjectReference< String > fieldNameRef )
        {
            Class< ? > javaType = owner.getJavaType();
            String fieldName = baseFieldName;

            int suffix = 0;

            // find clashes
            boolean clashing = false;
            boolean setField = true;
            do
            {
                T otherParameter = createdFields.get( fieldName );
                if ( otherParameter != null )
                {
                    clashing = true;
                    if ( ( otherParameter.getClass() == owner.getClass() )
                         && owner.getJavaType() == javaType )
                    {
                        // same one, so I don't mind
                        clashing = false;
                        setField = false;
                    }
                }

                if ( clashing )
                {
                    fieldName = baseFieldName + "_" + suffix;
                    suffix++;
                }
            }
            while ( clashing );

            if ( setField )
            {
                createdFields.put( fieldName, owner );
            }

            fieldNameRef.setValue( fieldName );

            return setField;
        }
    }

    /**
     * Helper class for creating instances of {@link CustomNativeObject}.
     *
     * @author Matthew Wilson
     */
    public static final class CustomNativeObjectHelper
    {
        private final String typeName;

        private Method factoryMethod;

        private Constructor< ? extends CustomNativeObject > constructor;

        /**
         * @param type
         *            the type
         */
        public CustomNativeObjectHelper( final Class< ? extends CustomNativeObject > type )
        {
            this.typeName = type.getName().replace( '.', '/' );

            try
            {
                this.factoryMethod = type.getMethod( "create", Pointer.class );
                if ( ( this.factoryMethod.getModifiers() & Modifier.STATIC ) == 0 )
                {
                    DniInternal.LOGGER.warning( "'create' method is not static: " + type.toString() );
                    this.factoryMethod = null;
                }
            }
            catch ( Exception exception )
            {
                DniInternal.LOGGER.log( Level.FINE, "No valid 'create' method on " + type.toString(), exception );
            }

            if ( this.factoryMethod == null )
            {
                try
                {
                    this.constructor = type.getConstructor( Pointer.class );
                }
                catch ( Exception exception )
                {
                    DniInternal.LOGGER.log( Level.FINE, "No valid constructor on " + type.toString(), exception );
                }
            }
        }

        /**
         * @return <code>true</code> iff valid
         */
        public boolean isValid()
        {
            return this.factoryMethod != null || this.constructor != null;
        }

        /**
         * Writes the create method invocation.  Assumes that the pointer as a
         * long is on the stack, and leaves an instance on the stack.
         *
         * @param methodVisitor
         *            the method visitor
         */
        public void writeCreate( final MethodVisitor methodVisitor )
        {
            Label nonNull = new Label();
            Label end = new Label();

            methodVisitor.visitInsn( DUP2 );
            methodVisitor.visitMethodInsn( INVOKESTATIC,
                                           STRUCTURE_HELPER_TYPE,
                                           "isZero",
                                           "(J)Z" );
            methodVisitor.visitJumpInsn( IFEQ, nonNull );

            methodVisitor.visitInsn( POP2 );
            methodVisitor.visitInsn( ACONST_NULL );

            methodVisitor.visitJumpInsn( Opcodes.GOTO, end );

            methodVisitor.visitLabel( nonNull );
            if ( this.factoryMethod != null )
            {
                methodVisitor.visitMethodInsn( INVOKESTATIC,
                                               POINTER_TYPE,
                                               "fromAddress",
                                               "(J)L" + POINTER_TYPE + ";" );

                methodVisitor.visitMethodInsn( INVOKESTATIC,
                                               this.typeName,
                                               "create",
                                               "(L" + POINTER_TYPE + ";)L" + this.typeName +";" );
            }
            else
            {
                assert this.constructor != null;

                methodVisitor.visitTypeInsn( NEW, this.typeName );

                // stack: addr1, addr2, obj

                methodVisitor.visitInsn( DUP_X2 );

                // stack: obj, addr1, addr2, obj

                methodVisitor.visitInsn( DUP_X2 );

                // stack: obj, obj, addr1, addr2, obj

                methodVisitor.visitInsn( POP );

                // stack: obj, obj, addr1, addr2

                methodVisitor.visitMethodInsn( INVOKESTATIC,
                                               POINTER_TYPE,
                                               "fromAddress",
                                               "(J)L" + POINTER_TYPE + ";" );

                // stack: obj, obj, ptr

                methodVisitor.visitMethodInsn( INVOKESPECIAL,
                                               this.typeName,
                                               "<init>",
                                               "(L" + DniTypes.POINTER_TYPE + ";)V" );

                // stack: obj
            }

            methodVisitor.visitLabel( end );
        }

    }


}
